package cn.virens.web.components.session;

import java.io.Serializable;
import java.time.Duration;
import java.time.Instant;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.UUID;

import org.springframework.session.Session;
import org.springframework.util.Assert;

/**
 * Spring Session 对象
 * 
 * @作者   : virens
 * @创建时间 :2018年1月15日 上午9:07:16
 */
public class CacheSession implements Session, Serializable {
	private static final long serialVersionUID = 7160779239673823561L;

	private String id;
	private String originalId;
	private Instant creationTime = Instant.now();
	private Instant lastAccessedTime = this.creationTime;
	private Map<String, Object> sessionAttrs = new HashMap<>();

	/**
	 * Defaults to 30 minutes.
	 */
	private Duration maxInactiveInterval = Duration.ofSeconds(1800);

	/**
	 * Creates a new instance with a secure randomly generated identifier.
	 */
	public CacheSession() {
		this(generateId());
	}

	/**
	 * Creates a new instance with the specified id. This is preferred to the
	 * default constructor when the id is known to prevent unnecessary consumption
	 * on entropy which can be slow.
	 *
	 * @param id the identifier to use
	 */
	public CacheSession(String id) {
		this.id = id;
		this.originalId = id;
	}

	/**
	 * Creates a new instance from the provided {@link Session}.
	 *
	 * @param session the {@link Session} to initialize this {@link Session} with.
	 *                Cannot be null.
	 */
	public CacheSession(Session session) {
		Assert.notNull(session, "session cannot be null");

		this.id = session.getId();
		this.originalId = session.getId();
		this.creationTime = session.getCreationTime();
		this.lastAccessedTime = session.getLastAccessedTime();
		this.maxInactiveInterval = session.getMaxInactiveInterval();

		// 转移session数据
		for (String attrName : session.getAttributeNames()) {
			Object attrValue = session.getAttribute(attrName);
			if (attrValue == null) {
				continue;
			}

			this.sessionAttrs.put(attrName, attrValue);
		}
	}

	@Override
	public String getId() {
		return this.id;
	}

	/**
	 * Sets the identifier for this {@link Session}. The id should be a secure
	 * random generated value to prevent malicious users from guessing this value.
	 * The default is a secure random generated identifier.
	 *
	 * @param  id the identifier for this session.
	 * @return
	 */
	public String setId(String id) {
		return (this.id = id);
	}

	@Override
	public boolean isExpired() {
		return isExpired(Instant.now());
	}

	protected boolean isExpired(Instant now) {
		if (this.maxInactiveInterval.isNegative()) { return false; }

		return now.minus(this.maxInactiveInterval).compareTo(this.lastAccessedTime) >= 0;
	}

	protected String getOriginalId() {
		return this.originalId;
	}

	protected void setOriginalId(String originalId) {
		this.originalId = originalId;
	}

	@Override
	public String changeSessionId() {
		return setId(generateId());
	}

	@Override
	public Instant getCreationTime() {
		return this.creationTime;
	}

	/**
	 * Sets the time that this {@link Session} was created. The default is when the
	 * {@link Session} was instantiated.
	 * 
	 * @param creationTime the time that this {@link Session} was created.
	 */
	public void setCreationTime(Instant creationTime) {
		this.creationTime = creationTime;
	}

	@Override
	public Instant getLastAccessedTime() {
		return this.lastAccessedTime;
	}

	@Override
	public void setLastAccessedTime(Instant lastAccessedTime) {
		this.lastAccessedTime = lastAccessedTime;
	}

	@Override
	public Duration getMaxInactiveInterval() {
		return this.maxInactiveInterval;
	}

	@Override
	public void setMaxInactiveInterval(Duration interval) {
		this.maxInactiveInterval = interval;
	}

	@Override
	@SuppressWarnings("unchecked")
	public <T> T getAttribute(String attributeName) {
		return (T) this.sessionAttrs.get(attributeName);
	}

	@Override
	public Set<String> getAttributeNames() {
		return this.sessionAttrs.keySet();
	}

	@Override
	public void setAttribute(String attributeName, Object attributeValue) {
		if (attributeValue == null) {
			this.removeAttribute(attributeName);
		} else {
			this.sessionAttrs.put(attributeName, attributeValue);
		}
	}

	@Override
	public void removeAttribute(String attributeName) {
		this.sessionAttrs.remove(attributeName);
	}

	@Override
	public boolean equals(Object obj) {
		return obj instanceof Session && this.id.equals(((Session) obj).getId());
	}

	@Override
	public int hashCode() {
		return this.id.hashCode();
	}

	private static String generateId() {
		return UUID.randomUUID().toString();
	}

}
